﻿using System;
using System.Collections.Generic;

namespace ExtensionMethodExtravaganza
{
	public static class ObjectExtensions
	{
		/// <summary>
		/// Returns a reference of the object as the type specified by TReturn if it "is" of the type
		/// specified by the subsequent generic type arguments.
		/// </summary>
		public static TReturn As<TReturn, T1, T2>(this object o) 
			where TReturn : class
			where T1 : TReturn
			where T2 : TReturn
		{
			return As<TReturn>(o, new[] { typeof(T1), typeof(T2) });
		}

		/// <summary>
		/// Returns a reference of the object as the type specified by TReturn if it "is" of the type
		/// specified by the subsequent generic type arguments.
		/// </summary>
		public static TReturn As<TReturn, T1, T2, T3>(this object o) 
			where TReturn : class
			where T1 : TReturn
			where T2 : TReturn
			where T3 : TReturn
		{
			return As<TReturn>(o, new[] { typeof(T1), typeof(T2), typeof(T3) });
		}

		/// <summary>
		/// Returns a reference of the object as the type specified by TReturn if it "is" of the type
		/// specified by the subsequent generic type arguments.
		/// </summary>
		public static TReturn As<TReturn, T1, T2, T3, T4>(this object o) 
			where TReturn : class
			where T1 : TReturn
			where T2 : TReturn
			where T3 : TReturn
			where T4 : TReturn
		{
			return As<TReturn>(o, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) });
		}

		/// <summary>
		/// Returns a reference of the object as the type specified by TReturn if it "is" of the type
		/// specified by the subsequent generic type arguments.
		/// </summary>
		public static TReturn As<TReturn, T1, T2, T3, T4, T5>(this object o)
			where TReturn : class
			where T1 : TReturn
			where T2 : TReturn
			where T3 : TReturn
			where T4 : TReturn
			where T5 : TReturn
		{
			return As<TReturn>(o, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) });
		}

		private static TReturn As<TReturn>(object o, IEnumerable<Type> types) where TReturn : class
		{
			if (o == null)
			{
				return null;
			}

			foreach (var type in types)
			{
				if (Is(o, type))
				{
					return (TReturn)o;
				}
			}

			return null;
		}

		/// <summary>
		/// Determines if the object is assignable from, or a subclass of, the types specified by the type parameters.
		/// </summary>
		public static bool Is<T1, T2>(this object o)
		{
			return Is(o, new[] { typeof(T1), typeof(T2) });
		}

		/// <summary>
		/// Determines if the object is assignable from, or a subclass of, the types specified by the type parameters.
		/// </summary>
		public static bool Is<T1, T2, T3>(this object o)
		{
			return Is(o, new[] { typeof(T1), typeof(T2), typeof(T3) });
		}

		/// <summary>
		/// Determines if the object is assignable from, or a subclass of, the types specified by the type parameters.
		/// </summary>
		public static bool Is<T1, T2, T3, T4>(this object o)
		{
			return Is(o, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) });
		}

		/// <summary>
		/// Determines if the object is assignable from, or a subclass of, the types specified by the type parameters.
		/// </summary>
		public static bool Is<T1, T2, T3, T4, T5>(this object o)
		{
			return Is(o, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) });
		}

		private static bool Is(object o, params Type[] types)
		{
			if (o == null)
			{
				return false;
			}

			foreach (var type in types)
			{
				if (type.IsAssignableFrom(o.GetType()) || o.GetType().IsSubclassOf(type))
				{
					return true;
				}
			}

			return false;
		}


		public static T[] CreateArray<T>(this T obj)
		{
			return new[] { obj };
		}
	}
}
